package beantools;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.google.common.collect.Collections2.filter;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static org.codehaus.groovy.runtime.InvokerHelper.asList;

public class BeanTools {
	private static Predicate<Field> notIgnoredField = new Predicate<Field>() { public boolean apply(Field f) { return !f.isAnnotationPresent(Ignore.class); } };

	@java.lang.annotation.Retention(RUNTIME)
	@java.lang.annotation.Target(FIELD)
	public @interface Ignore {}

	public static void copyProperties(Object target, Object src) throws IllegalAccessException {

		for(Field field : allNotIgnoredFields(src)){
			try {
				setField(target, src, field);
			} catch (Exception e) {
				e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
			}
		}
	}

	public static Map describe(Object o) {
        try {
            return describe(o, Iterables.toArray(allNotIgnoredFields(o), Field.class));
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

	public static Map describe(Object o, String... ignoredFields) throws IllegalAccessException {
		return describe(o, removeIgnoredFields(allNotIgnoredFields(o), ignoredFields));
	}

	private static Field[] removeIgnoredFields(Iterable<Field> fields, String[] ignoredFields) {		
		List<Field> res = new ArrayList<Field>();
		for(Field f: fields){
			if (!asList(ignoredFields).contains(f.getName())) res.add(f);
		}
		return res.toArray(new Field[res.size()]);
	}

	private static Map describe(Object o, Field[] fields) throws IllegalAccessException {
		Map res = new HashMap();
		for(Field f : fields){
			res.put(f.getName(), getProperty(o, f));
		}
		return res;
	}

	private static void setField(Object target, Object src, Field field) throws IllegalAccessException {
		boolean accesibility  = field.isAccessible();
		field.setAccessible(true);
		try {
			field.set(target, field.get(src));
		} finally {
			field.setAccessible(accesibility);
		}
	}

	private static Iterable<Field> allNotIgnoredFields(Object src) {

        Class clazz = src.getClass();
        ArrayList fields = new ArrayList();
        do{
            fields.addAll( filter(asList(clazz.getDeclaredFields()), notIgnoredField));
            clazz = clazz.getSuperclass();
        }while (clazz != null);

        return fields;
	}

	private static Object getProperty(Object o, Field f) throws IllegalAccessException {
		boolean accesibility  = f.isAccessible();
		f.setAccessible(true);
		try {
			return f.get(o);
		} finally {
			f.setAccessible(accesibility);
		}
	}

}
